﻿// Inner Fire 游戏引擎库
// AnimatorChannel - 通用播放接口（播放输出）
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 这东西其实应该叫PlayableChannel的，但为了和Unity统一（其实也没统一什么）
// 就把他叫成Output了。但是和Unity的Playable的Output完全不是一回事。
// 这东西更像是Animator中的一个层。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-04-30

#include "animator_channel.h"
#include <game/animation/animator.h>
#include <game/animation/armature.h>
#include <knumber.h>

namespace ifire::game {

AnimatorChannel::AnimatorChannel(
    Animator* animator, ikit::sstr channel_name, int channel_index)
    : animator_(animator)
    , name(channel_name)
    , index(channel_index)
    , current()
    , next()
    , state(AnimatorChannelState::Idle)
    , slot_offset_(0) {
  snapshots_ =
      std::make_unique<Snapshots>(animator_->GetArmature()->GetBoneCount());
}

void AnimatorChannel::Play(const Playable& p, float transition_time) {
  // 检查是否拥有Transition时间，处理不同
  if (state == AnimatorChannelState::Idle || ikit::is_zero(transition_time)) {
    // 直接覆盖
    current = p;
    // 设置状态为播放
    state = AnimatorChannelState::Play;
  }else{    
    // 设置下一个项目
    next = p;
    // 设置transition状态
    transition_time_ = transition_time;
    transition_tick_ = 0.0f;
    state = AnimatorChannelState::Transition;
  }
}


void AnimatorChannel::Clear() { current = Playable(); }

void AnimatorChannel::Play() {
  // 如果循环方式为1帧，则表示在选择的时候就已经播放，不进行任何改动。
  if (current.loop == PlayMethod::Frame) {
    return;
  }

  // 如果没有动画，则使用Pose
  if (current.empty()) {
    // 这里直接使用Mix
    animator_->GetStaticPose(
        snapshots_->GetPrimary().transforms, ARMATURE_STATIC_POSE);
    return;
  }

  // 如果状态为Play，直接执行Current
  slot_offset_ = 0;
  if (state == AnimatorChannelState::Play) {
    Execute(current);
  } else if (state == AnimatorChannelState::Transition) {
    Execute(current);
    slot_offset_ = 1;
    Execute(next);
    MixTransition();
  }
}

void AnimatorChannel::Execute(Playable& play) {
  // 清空Transform
  snapshots_->ClearUsed();

  // 如果属性单个项目，直接使用0位置播放
  if (play.type == PlayableType::Clip) {
    // 执行一个运算
    ExecuteSlot(play.clip, play, 0, 1.0f);
  } else if (play.type == PlayableType::Blend) {
    // 对于单线性的播放，不需要使用复杂的算法，只需要获得2个项目
    if (play.blend->IsLinear()) {
      // 计算权重
      BlendItemWeight a, b;
      play.blend->GetRay(0).Calculate(play.value.x, a, b);
      // 如果出现只有一方的项目，则只播放单个
      if (b.clip == nullptr) { // 如果是单个，必然是B是空白，不会A是空白。
        // 执行一个运算
        ExecuteSlot(a.clip, play, 0, 1.0f);
      } else {
        // 使用两个运算
        ExecuteSlot(a.clip, play, 0, a.weight);
        ExecuteSlot(b.clip, play, 1, b.weight);
        // 对权重进行混合
        MixSlot(0, 1, 0);
      }
    }
  }
}

void AnimatorChannel::ExecuteSlot(
    AnimationClip* clip, const Playable& play, int slot_index, float weight) {
  // 处理CLIP问题
  if (!clip) {
    return;
  }

  // 得到快照用于设置
  auto& slot = GetSlot(slot_index + slot_offset_);

  // 通过时间和method计算帧
  slot.SetCurrent(clip, clip->CalcFrame(play.time, play.loop));

  // 设置Transform
  slot.SetUsed(true);
  slot.weight = weight;

  // 根据类型决定播放项目
  if (clip->IsHumanoid()) {
    animator_->GetHumanoid()->CalcAnimeFrame(slot);
  } else {
    animator_->SetAnimeFrame(slot);
  }
}

Armature* AnimatorChannel::GetArmature() { return animator_->GetArmature(); }

Snapshot& AnimatorChannel::GetSlot(int slot) {
  if (!snapshots_) {
    snapshots_ = std::make_unique<Snapshots>(GetArmature()->GetBoneCount());
  }
  return snapshots_->Get(slot);
}

Snapshot& AnimatorChannel::GetMix() { return snapshots_->GetPrimary(); }

void AnimatorChannel::MixTransition() {
  // TIPS：
  // Current的播放结果是存在0上面，Next是存在1上面。

  // 步进权重
  transition_tick_ += animator_->deltaTime;
  // 如果权重已经大于上限，则使用next，并且结束Transition状态
  if (transition_tick_ >= transition_time_) {
    snapshots_->Pick(1);
    current = next;
    state = AnimatorChannelState::Play;
    return;
  }

  // 如果仍然在Mix范围之中，则设置权重
  float percent = transition_tick_ / transition_time_;
  GetSlot(0).weight = percent;
  GetSlot(1).weight = 1.0f - percent;
  // 提取权重到0
  snapshots_->Mix(0, 1, 0);
}

void AnimatorChannel::MixSlot(int a, int b, int to_c) {
  snapshots_->Mix(a + slot_offset_, b + slot_offset_, to_c + slot_offset_);
}

} // namespace ifire::game